﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Crayon;
using Krilib;
using Krilib.Control;
using Krilib.Control.Surface.MessageObjects;
using Krilib.Control.Surface;
using Krilib.Data;

namespace Wassrile.Operation.Web
{
    public class StatusReceiver : IDisposable
    {
        private bool Initialized = false;
        Timer timer;
        public StatusReceiver()
        {
            timer = new Timer(
                new TimerCallback(Tick),
                null,
                Timeout.Infinite,
                Timeout.Infinite);
        }

        public void Init()
        {
            if (!Initialized) return;
            timer.Change((long)0, (long)Core.Config.Receive.recvInterval * 1000);
        }

        public void Suspend()
        {
            timer.Change((long)Timeout.Infinite, (long)Core.Config.Receive.recvInterval * 1000);
        }

        public void Dispose()
        {
            timer.Dispose();
        }

        const string TimelineURI = "http://api.wassr.jp/statuses/friends_timeline.json";
        const string PublicTimelineURI = "http://api.wassr.jp/statuses/public_timeline.json";
        static string MyTimelineURI
        {
            get{
                return "http://api.wassr.jp/statuses/user_timeline.json?id=" + Core.Config.account.UserName;
            }
        }
        const string ReplyForMeURI = "http://api.wassr.jp/statuses/replies.json";
        const string ChannelFeedURI = "http://api.wassr.jp/channel_message/list.json?name_en=";
        static string GetChannelListURI
        {
            get { return "http://api.wassr.jp/channel_user/user_list.json?login_id=" + Core.Config.account.UserName; }
        }
        const string PagePrefix = "?page=";

        DateTime prevDt = DateTime.Now;
        private void Tick(object o)
        {
            if (!Initialized)
            {
                Suspend();
                return;
            }
            DoReceive();
        }

        public void DoReceive()
        {
            bool checkReplyFeed = false;
            var span = DateTime.Now - prevDt;
            if (span.Seconds >= Core.Config.Receive.replyCheckInterval)
            {
                prevDt = DateTime.Now;
                checkReplyFeed = true;
            }
            ReceiveTimeline(
                TimelineURI,
                Core.Config.Receive.getHistoryNum,
                Core.Config.Receive.traceFav);
            ReceiveTimeline(
                MyTimelineURI,
                1,
                Core.Config.Receive.traceFav);
            if (Core.Plugin.WOperation.Status.RecvGlobalTimeline)
            {
                ReceiveTimeline(
                    PublicTimelineURI,
                    Core.Config.Detail.Extra.PublicTimelineRecvIdx,
                    true);
            }
            if (checkReplyFeed)
            {
                ReceiveTimeline(
                    ReplyForMeURI,
                Core.Config.Receive.getHistoryNum,
                Core.Config.Receive.traceFav);
            }

            //ch
            ReceiveCh(true);
        }

        const string TestURI = "http://api.wassr.jp/statuses/show.json";
        const string GetFriendsURI = "http://api.wassr.jp/statuses/friends.json";
        const string GetFollowersURI = "http://api.wassr.jp/statuses/followers.json";
        public void InitialReceive()
        {
            Initialized = false;
            //check account enability
            var test = Web.Connect.WebOperation(new Uri(TestURI), "GET");
            if (!test.Succeeded)
            {
                Bridge.DebugReporter.AddReport("[Wassr]アカウントの正当性を確認できませんでした。再設定するか、Krileを再起動してください。");
                return;
            }

            //Get users data and set following data state
            var ret = Web.Connect.DownloadWithCallback<User[]>
                (new Uri(GetFriendsURI),
                true,
                true,
                new NetConnect.DStreamCallback<User[]>(Web.ReadJson.GetUsers),
                "GET");
            if (ret.Succeeded && ret.Data != null)
            {
                foreach (var d in ret.Data)
                {
                    d.following = true;
                }
            }
            Web.Connect.DownloadWithCallback<User[]>
                (new Uri(GetFollowersURI),
                true,
                true,
                new NetConnect.DStreamCallback<User[]>(Web.ReadJson.GetUsers),
                "GET");
            if (Core.Config.Detail.BootReceives.myTL)
            {
                ReceiveTimeline(
                    MyTimelineURI,
                    1,
                    false, false);
            }
            if (Core.Config.Detail.BootReceives.replyTL)
            {
                ReceiveTimeline(
                    ReplyForMeURI,
                    Core.Config.Detail.BootReceives.replyTLPageNum,
                    false, false);
            }
            if (Core.Config.Detail.BootReceives.friendTL)
            {
                ReceiveTimeline(
                    TimelineURI,
                    Core.Config.Detail.BootReceives.friendTLPageNum,
                    false);
            }
            ReceiveCh(Core.Config.Detail.BootReceives.channelFeed, false);
            Initialized = true;
            Init();
        }

        private void ReceiveCh(bool recvChTL)
        {
            ReceiveCh(recvChTL, true);
        }

        private void ReceiveCh(bool recvChTL, bool isnew)
        {
            var opResult = Web.Connect.DownloadWithCallback<Dictionary<string, DateTime>>
                (new Uri(GetChannelListURI),
                true,
                true,
                new NetConnect.DStreamCallback<Dictionary<string, DateTime>>(Web.ReadJson.GetChList),
                "GET");
            if (!opResult.Succeeded || opResult.Data == null)
            {
                Bridge.DebugReporter.AddReport(new Krilib.Bridges.DebugReport("Wassr:受信エラー(" + GetChannelListURI + ")", Krilib.Bridges.DebugReport.Priorities.Info));
                return;
            }
            List<string> updated = new List<string>();
            foreach (var k in opResult.Data.Keys)
            {
                if (DataMan.IsChannelRecvRequired(k, opResult.Data[k]))
                    updated.Add(k);
            }
            if (updated.Count != 0 && recvChTL)
            {
                foreach (var k in updated)
                {
                    ReceiveChTimeline(
                        ChannelFeedURI + k,
                        1,
                        false, isnew);
                }
            }
        }

        public void ReceiveTimeline(string uri, uint pageTraceIndex, bool forced)
        {
            ReceiveTimeline(uri, pageTraceIndex, forced, true);
        }

        public void ReceiveTimeline(string uri, uint pageTraceIndex, bool forced, bool isnew)
        {
            for (uint p = 1; p <= pageTraceIndex; p++)
            {
                if (!Receive(
                    uri,
                    p,
                    new NetConnect.DStreamCallback<Wassrile.Operation.DataClasses.WStatus[]>(Web.ReadJson.GetWassrStatus), isnew)
                    && !forced)
                {
                    break;
                }
            }
        }

        public void ReceiveChTimeline(string uri, uint pageTraceIndex,bool forced, bool isnew)
        {
            for (uint p = 1; p <= pageTraceIndex; p++)
            {
                if (!Receive(
                    uri,
                    p,
                    new NetConnect.DStreamCallback<Wassrile.Operation.DataClasses.WStatus[]>(Web.ReadJson.GetWassrStatusFromChFeed), isnew) &&
                    !forced)
                {
                    break;
                }
            }
        }

        private bool Receive(
            string uri,
            uint curPage,
            NetConnect.DStreamCallback<DataClasses.WStatus[]> callback,
            bool isnew)
        {
            if (curPage > 1)
                uri += PagePrefix + curPage.ToString();
            var opResult = Web.Connect.DownloadWithCallback<DataClasses.WStatus[]>
                (new Uri(uri),
                true,
                true,
                callback,
                "GET");
            if (!opResult.Succeeded || opResult.Data == null)
            {
                Bridge.DebugReporter.AddReport(new Krilib.Bridges.DebugReport("Wassr:受信エラー(" + uri + ")", Krilib.Bridges.DebugReport.Priorities.Info));
                return false;
            }
            if (opResult.Data.Length == 0)
                //no data
                return false;
            if (Web.DataMan.WidExists(opResult.Data[opResult.Data.Length - 1].id))
                return false;
            StatusTrapper(opResult.Data, isnew);
            return true;
        }

        private void StatusTrapper(DataClasses.WStatus[] statuses, bool isnew)
        {
            List<string> idStore = new List<string>();
            Func<DataClasses.WStatus, DataClasses.WStatus> func = (n) => { idStore.Add(n.id); return DecorateMessage(n); };
            var iteration = from DataClasses.WStatus ws in statuses
                            where !idStore.Contains(ws.id)
                            select func(ws);
            DataMan.RegistNewStatus(iteration, isnew);
        }
  
        /// <summary>
        /// Status message decorator
        /// </summary>
        /// <param name="ws"></param>
        /// <returns></returns>
        private DataClasses.WStatus DecorateMessage(DataClasses.WStatus ws)
        {
            ws.text.Original = ws.text.Original.Replace('\r', ' ').Replace('\n', ' ');
            List<MessageObject> moStock = new List<MessageObject>(CreateMsgObject(ws.text.Original));
            if (ws.LinkedImage != null)
            {
                moStock.Add(new Image(ws.LinkedImage));
            }
            var favd = new Controls.FavDigest(ref ws);
            ws.digest = favd;
            moStock.Add(favd);
            ws.text.MessageObjects = moStock.ToArray();
            if (ws.LocationName != null)
            {
                if (ws.text.FooterText == String.Empty || ws.text.FooterText == null)
                    ws.text.FooterText = "[L:" + ws.LocationName + "]";
                else
                    ws.text.FooterText = "[L:" + ws.LocationName + "] " + ws.text.FooterText;
            }
            return ws;
        }

        Regex PictoRegex = new Regex(@"([\uE001-\uF0FF])", RegexOptions.Singleline | RegexOptions.Compiled);
        Regex ColorRegex = new Regex(@"{color:(aqua|blue|chocolate|darkblue|fuchsia|gold|gray|green|lightgreen|lime|olive|orange|pink|darkred|plum|purple|red|silver|skyblue):(.*?)}", RegexOptions.Singleline | RegexOptions.Compiled);

        const string WassrProfilePage = "http://wassr.jp/user/";
        private MessageObject[] CreateMsgObject(string msg)
        {
            msg = msg.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\r", "").Replace("\n", "");
            //pre-processor
            msg = ColorRegex.Replace(msg, "<#$1>$2<");
            msg = PictoRegex.Replace(msg, "<*>$1<");
            msg = Crayon.Regexs.URLRegex.Replace(msg, "$1<$2>$2<");
            msg = Crayon.Regexs.AtRegex.Replace(msg, "@<" + WassrProfilePage + "$1>$1<$2");
            List<MessageObject> objStock = new List<MessageObject>();
            foreach (var item in msg.Split(new[] { '<' }, StringSplitOptions.RemoveEmptyEntries))
            {
                if (item.Contains('>'))
                {
                    var iArray = item.Split('>');
                    switch(iArray[0][0])
                    {
                        case '*':
                            //pictogram
                            objStock.Add(new Controls.Pictogram(Convert.ToString((int)iArray[1][0], 16).ToUpper()));
                            continue;
                        case '#':
                            //colored string
                            objStock.Add(
                                new Text(
                                    iArray[1].Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&"),
                                    System.Drawing.Color.FromName(iArray[0].Substring(1)))
                                    );
                            continue;
                        default:
                            //link
                            if (Uri.IsWellFormedUriString(iArray[0], UriKind.RelativeOrAbsolute))
                            {
                                try
                                {
                                    Uri target = new Uri(iArray[0]);
                                    objStock.Add(new Hyperlink(target, iArray[1]));
                                    if (Core.Config.Detail.extractGyazo &&
                                        target.Host == "gyazo.com")
                                    {
                                        objStock.Add(new Image(target));
                                        Bridge.ImageManager.ReserveImage(target, false);
                                    }
                                    continue;
                                }
                                catch (UriFormatException) { }
                            }
                            break;
                    }
                }
                objStock.Add(new Text(item.Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&")));
            }
            return objStock.ToArray();
        }
    }
}
